﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net.Sockets;

namespace Chat
{
    public class UserSocketEventArgs : EventArgs
    {
        public string Message { get; private set; }
        public UserSocket UserSocket { get; private set; }

        public UserSocketEventArgs(UserSocket userSocket, string message)
        {
            this.UserSocket = userSocket;
            this.Message = message;
        }

        public UserSocketEventArgs(UserSocket userSocket)
            : this(userSocket, string.Empty)
        {

        }
    }

    public enum ReceiveMode : byte
    {
        Manual = 1,
        Automatic = 2
    }

    public class UserSocket
    {
        public event EventHandler<UserSocketEventArgs> Error = delegate { };
        public event EventHandler<MessageEventArgs> NewMessage = delegate { };

        protected Socket _socket;

        protected byte[] _unprocessedBuffer = new byte[0];
        protected int _unprocessedBufferIndex = 0;

        public ReceiveMode ReceiveMode { get; private set; }
        
        public bool DataAvailable
        {
            get
            {
#if SILVERLIGHT
                return !this.Receiving;
#else
                return _socket.Available > 0 && !this.Receiving;
#endif
            }
        }

        public bool Receiving { get; private set; }

        public string Name { get; set; }

        public UserSocket(Socket socket, ReceiveMode receiveMode)
        {
            if (socket == null)
                throw new NullReferenceException("Socket cannot be null");

            _socket = socket;

            this.ReceiveMode = receiveMode;

            if (this.ReceiveMode == ReceiveMode.Automatic)
                doReceiveMessages();
        }

        public void Send(Message message)
        {
            try
            {


                SocketAsyncEventArgs args = new SocketAsyncEventArgs();
                byte[] buffer = message.ToPacketBytes();
                args.SetBuffer(buffer, 0, buffer.Length);
                args.Completed += new EventHandler<SocketAsyncEventArgs>(send_Completed);

                if (!_socket.SendAsync(args)) // returns false if operation completed successfully
                    send_Completed(this, args);
            }
            catch (SocketException ex)
            {
                Error(this, new UserSocketEventArgs(this, "Error Sending To Client: " + ex.Message));
            }
        }

        public void ReceiveMessages()
        {
            if (this.ReceiveMode == ReceiveMode.Automatic)
                throw new InvalidOperationException("ReceiveMessagess not allowed in Automatic Receive Mode");
            else if (this.Receiving)
                throw new InvalidOperationException("Cannot make concurrent calls to ReceiveMessages");

            doReceiveMessages();
        }

        private void doReceiveMessages()
        {
            this.Receiving = true;

            byte[] buffer = new byte[1024];

            SocketAsyncEventArgs args = new SocketAsyncEventArgs();
            args.SetBuffer(buffer, 0, buffer.Length);
            args.Completed += new EventHandler<SocketAsyncEventArgs>(receive_Completed);
            
            if (!_socket.ReceiveAsync(args)) // returns false if completed synchronously
                receive_Completed(this, args);
        }

        private void resizeUnprocessedBufferIfNecessary(int incomingBufferLength)
        {
            if (_unprocessedBuffer.Length - _unprocessedBufferIndex < incomingBufferLength)
            {
                // resize to the current buffer length + whatever is coming in
                // this covers the worst case that our buffer is completely full
                byte[] newUnprocessedBuffer = new byte[_unprocessedBuffer.Length + incomingBufferLength];
                Array.Copy(_unprocessedBuffer, newUnprocessedBuffer, _unprocessedBuffer.Length);

                _unprocessedBuffer = newUnprocessedBuffer;
            }
        }

        private int processMessagesFromBuffer()
        {
            int numberOfMessagesProcessed = 0;
            bool parsedPacket = false;

            do
            {
                if (_unprocessedBufferIndex > 4)
                {
                    int packetSize = BitConverter.ToInt32(_unprocessedBuffer, 0);

                    if (packetSize <= _unprocessedBuffer.Length)
                    {
                        byte[] data = new byte[packetSize];
                        Array.Copy(_unprocessedBuffer, data, packetSize);

                        Message message = Message.FromPacketBytes(data);

                        // Eat the processed packet out of the packet
                        for (int n = packetSize; n < _unprocessedBufferIndex; n++)
                        {
                            _unprocessedBuffer[n - packetSize] = _unprocessedBuffer[n];
                        }

                        // And reset the unprocessedBufferIndex
                        _unprocessedBufferIndex -= packetSize;

                        Array.Clear(_unprocessedBuffer, _unprocessedBufferIndex, packetSize);

                        NewMessage(this, new MessageEventArgs(message));

                        parsedPacket = true;
                        numberOfMessagesProcessed++;
                    }
                    else
                        parsedPacket = false;
                }
                else
                    parsedPacket = false;

            } while (parsedPacket);

            return numberOfMessagesProcessed;
        }

        void receive_Completed(object sender, SocketAsyncEventArgs e)
        {
            try
            {
                if (e.SocketError == SocketError.Success)
                {
                    resizeUnprocessedBufferIfNecessary(e.BytesTransferred);
                    Array.Copy(e.Buffer, 0, _unprocessedBuffer, _unprocessedBufferIndex, e.BytesTransferred);
                    _unprocessedBufferIndex += e.BytesTransferred;

                    if ((_unprocessedBufferIndex > 0 && processMessagesFromBuffer() == 0) || this.ReceiveMode == ReceiveMode.Automatic)
                        doReceiveMessages();
                    else
                        this.Receiving = false;
                }
                else
                {
                    Error(this, new UserSocketEventArgs(this, e.SocketError.ToString()));
                }
            }
            catch (Exception ex)
            {
                Error(this, new UserSocketEventArgs(this, "Error Receiving: " + ex.Message));
            }
        }

        void send_Completed(object sender, SocketAsyncEventArgs e)
        {
            if (e.SocketError != SocketError.Success)
            {
                Error(this, new UserSocketEventArgs(this, e.SocketError.ToString()));
            }
        }

        internal void Close()
        {
            // May need to add a flag or lock to ensure no access after this point
            _socket.Close(1);
        }
    }
}
